﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.ServiceModel;

namespace WcfDynamicProxy
{
    /// <summary>
    /// Формирует асинхронный интерфейс из синхронного. (Производит emit асинхронного интефрейса)      
    /// </summary>
    internal class AsyncServiceInterfaceGenerator
    {
        private readonly ModuleBuilder module;
        private readonly Type syncInterface;

        private TypeBuilder asyncInterface;

        public AsyncServiceInterfaceGenerator(ModuleBuilder module, Type syncInterface)
        {
            this.module = module;
            this.syncInterface = syncInterface;
        }

        public Type Generate()
        {
            DefineAsyncInterfaceType();
            DefineAsyncMethodPairs();

            return asyncInterface.CreateType();
        }

        private void DefineAsyncInterfaceType()
        {
            string asyncInterfaceName = syncInterface.Namespace + "." + syncInterface.Name + "Async";

            asyncInterface = module.DefineType(asyncInterfaceName,
                                               TypeAttributes.Public | TypeAttributes.Interface |
                                               TypeAttributes.Abstract);

            asyncInterface.SetCustomAttribute(CreateAttribute<ServiceContractAttribute>("Name", syncInterface.Name));
        }

        private void DefineAsyncMethodPairs()
        {
            foreach (MethodInfo method in GetOperationContractMethods())
            {
                DefineAsyncMethodPair(method);
            }
        }

        private void DefineAsyncMethodPair(MethodInfo method)
        {
            DefineBeginMethod(method);
            DefineEndMethod(method);
        }

        private void DefineBeginMethod(MethodInfo method)
        {
            List<Type> parameterTypes = CollectParameterTypes(method);

            MethodBuilder methodBuilder = asyncInterface.DefineMethod("Begin" + method.Name,
                                                                      MethodAttributes.Public |
                                                                      MethodAttributes.Abstract |
                                                                      MethodAttributes.Virtual,
                                                                      typeof (IAsyncResult), parameterTypes.ToArray());
            DefineParameterNames(method, methodBuilder);

            DefineOperationContractAttribute(methodBuilder);
            DefineFaultContractAttribute(method, methodBuilder);
        }

        private static void DefineParameterNames(MethodInfo syncMethod, MethodBuilder asyncMethod)
        {
            int position = 1;
            foreach (ParameterInfo parameter in syncMethod.GetParameters())
            {
                asyncMethod.DefineParameter(position++, ParameterAttributes.In, parameter.Name);
            }
        }

        private static List<Type> CollectParameterTypes(MethodInfo method)
        {
            return new List<Type>(method.GetParameters().Select(parameter => parameter.ParameterType))
                       {
                           typeof (AsyncCallback),
                           typeof (object)
                       };
        }

        private static void DefineOperationContractAttribute(MethodBuilder methodBuilder)
        {
            methodBuilder.SetCustomAttribute(CreateAttribute<OperationContractAttribute>("AsyncPattern", true));
        }

        private static void DefineFaultContractAttribute(MethodInfo method, MethodBuilder methodBuilder)
        {
            if (!HasAttribute(method, typeof (FaultContractAttribute)))
                return;

            var attributeBuilders =
                method.GetCustomAttributes(typeof(FaultContractAttribute), true).Cast<FaultContractAttribute>().Select(
                    faultContract => new CustomAttributeBuilder(
                                           typeof (FaultContractAttribute).GetConstructor(new[] {typeof (Type)}),
                                           new object[] {faultContract.DetailType}));

            foreach (var attributeBulder in attributeBuilders)
            {
                methodBuilder.SetCustomAttribute(attributeBulder);
            }
        }

        private void DefineEndMethod(MethodInfo method)
        {
            asyncInterface.DefineMethod("End" + method.Name,
                                        MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual,
                                        method.ReturnType, new[] {typeof (IAsyncResult)});
        }

        private IEnumerable<MethodInfo> GetOperationContractMethods()
        {
            return syncInterface.GetMethods().Where(method => HasAttribute(method, typeof (OperationContractAttribute)));
        }

        private static bool HasAttribute(MethodInfo method, Type attribute)
        {
            return method.GetCustomAttributes(attribute, true).Length > 0;
        }

        private static CustomAttributeBuilder CreateAttribute<TAttribute>(string property, object value)
            where TAttribute : Attribute
        {
            Type attributeType = typeof (TAttribute);

            return new CustomAttributeBuilder(attributeType.GetConstructor(new Type[0]), new object[0],
                                              new[] {attributeType.GetProperty(property)}, new[] {value});
        }
    }
}